#include "napi/native_api.h"
#include <cstdint>
#include <cstdio>
#include <iostream>
#include <vector>
#include <stdio.h>
#include <hilog/log.h>
#include <unistd.h>

struct CallbackData {
    napi_async_work asyncWork = nullptr;
    napi_ref callbackRef = nullptr;
    std::string args[2];
    std::string result;
    std::vector<uint8_t> result_buf;
    napi_env env;
    napi_threadsafe_function safe_func;
};

static void CallJs(napi_env env, napi_value jsCallback, void *context, void *data) {
    napi_value undefined;
    napi_get_undefined(env, &undefined);

    napi_value argv;
    napi_create_string_utf8(env, (const char *)data, NAPI_AUTO_LENGTH, &argv);

    napi_value result;
    napi_call_function(env, undefined, jsCallback, 1, &argv, &result);
    free(data);
}

static int exec_cmd(const std::string &cmd_str, const std::string &path_str,
                    std::function<void(const std::string &)> callback) {
    OH_LOG_Print(LOG_APP, LOG_DEBUG, 1, "gitGuiCpp", "[exec_cmd] cmd_str: %{public}s", cmd_str.c_str());

    if ((path_str.length() != 0) && (chdir(path_str.c_str()) != 0)) {
        std::cerr << "[exec_cmd] chdir failed: " << path_str << std::endl;
        return 1;
    }

    FILE *fp = popen(cmd_str.c_str(), "r");
    if (!fp) {
        std::cerr << "[exec_cmd] popen failed: " << cmd_str << std::endl;
        return 1;
    }

    char buffer[1024] = {0};

    // Flag to track if empty line has been sent
    bool sentEmptyLine = false;

    while (fgets(buffer, sizeof(buffer), fp) != nullptr) {
        std::string line(buffer);

        // Check if the line is empty and not yet sent
        if (line == "\n" && !sentEmptyLine) {
            callback(line);
            // Mark empty line as sent
            sentEmptyLine = true;
        } else if (line != "\n") {
            // Send non-empty lines
            callback(line);
        }
    }

    pclose(fp);

    // Send EOF marker after reading all lines
    callback("[EOF]");

    return 0;
}

static int exec_cmd(const std::string &cmd_str, const std::string &path_str, std::string &output) {
    // OH_LOG_Print(LOG_APP, LOG_DEBUG, 1, "gitGuiCpp", "[exec_cmd] path_str: %{public}s", path_str.c_str());
    OH_LOG_Print(LOG_APP, LOG_DEBUG, 1, "gitGuiCpp", "[exec_cmd] cmd_str: %{public}s", cmd_str.c_str());
    if ((path_str.length() != 0) && (chdir(path_str.c_str()) != 0)) {
        OH_LOG_Print(LOG_APP, LOG_ERROR, 1, "gitGuiCpp", "[exec_cmd] chdir failed: %{public}s", path_str.c_str());
        return 1;
    }

    FILE *fp = popen(cmd_str.c_str(), "r");
    if (!fp) {
        OH_LOG_Print(LOG_APP, LOG_ERROR, 1, "gitGuiCpp", "[exec_cmd] popen failed: %{public}s", cmd_str.c_str());
        return 1;
    }

    std::vector<char> buf(1024);
    output.clear();

    size_t bytesRead;
    while ((bytesRead = fread(buf.data(), 1, buf.size(), fp)) > 0) {
        output.append(buf.data(), bytesRead);
    }

    pclose(fp);
    return 0;
}

static int exec_cmd(const std::string &cmd_str, const std::string &path_str, std::vector<uint8_t> &output) {
    // OH_LOG_Print(LOG_APP, LOG_DEBUG, 1, "gitGuiCpp", "[exec_cmd] path_str: %{public}s", path_str.c_str());
    OH_LOG_Print(LOG_APP, LOG_DEBUG, 1, "gitGuiCpp", "[exec_cmd] cmd_str: %{public}s", cmd_str.c_str());
    if ((path_str.length() != 0) && (chdir(path_str.c_str()) != 0)) {
        OH_LOG_Print(LOG_APP, LOG_ERROR, 1, "gitGuiCpp", "[exec_cmd] chdir failed: %{public}s", path_str.c_str());
        return 1;
    }

    FILE *fp = popen(cmd_str.c_str(), "r");
    if (!fp) {
        OH_LOG_Print(LOG_APP, LOG_ERROR, 1, "gitGuiCpp", "[exec_cmd] popen failed: %{public}s", cmd_str.c_str());
        return 1;
    }

    std::vector<uint8_t> buf(1024);
    output.clear();

    size_t bytesRead;
    while ((bytesRead = fread(buf.data(), 1, buf.size(), fp)) > 0) {
        output.insert(output.end(), buf.begin(), buf.begin() + bytesRead);
    }

    pclose(fp);
    return 0;
}

static void create_array_buffer(napi_env env, std::vector<uint8_t> &data, napi_value *result) {
    napi_value output_buffer;
    void *output_ptr = NULL;
    napi_create_arraybuffer(env, data.size() * sizeof(uint8_t), &output_ptr, &output_buffer);
    napi_create_typedarray(env, napi_uint8_array, data.size(), output_buffer, 0, result);

    uint8_t *output_bytes = (uint8_t *)output_ptr;
    std::memcpy(output_bytes, data.data(), data.size() * sizeof(uint8_t));
}

static void SysCallByLineCompleteCB(napi_env env, napi_status status, void *data) {
    CallbackData *callbackData = reinterpret_cast<CallbackData *>(data);

    napi_release_threadsafe_function(callbackData->safe_func, napi_tsfn_release);
    napi_delete_reference(env, callbackData->callbackRef);
    napi_delete_async_work(env, callbackData->asyncWork);
    delete callbackData;
}

static void SysCallBufExecuteCB(napi_env env, void *data) {
    CallbackData *callbackData = reinterpret_cast<CallbackData *>(data);
    exec_cmd(callbackData->args[0], callbackData->args[1], callbackData->result_buf);
}

static void SysCallBufCompleteCB(napi_env env, napi_status status, void *data) {
    CallbackData *callbackData = reinterpret_cast<CallbackData *>(data);
    napi_value callbackArg[1] = {nullptr};
    create_array_buffer(env, callbackData->result_buf, &callbackArg[0]);

    napi_value callback = nullptr;
    napi_get_reference_value(env, callbackData->callbackRef, &callback);

    napi_value result;
    napi_value undefined;
    napi_get_undefined(env, &undefined);
    napi_call_function(env, undefined, callback, 1, callbackArg, &result);

    napi_delete_reference(env, callbackData->callbackRef);
    napi_delete_async_work(env, callbackData->asyncWork);
    delete callbackData;
}

static void SysCallExecuteCB(napi_env env, void *data) {
    CallbackData *callbackData = reinterpret_cast<CallbackData *>(data);
    exec_cmd(callbackData->args[0], callbackData->args[1], callbackData->result);
}

static void SysCallCompleteCB(napi_env env, napi_status status, void *data) {
    CallbackData *callbackData = reinterpret_cast<CallbackData *>(data);
    napi_value callbackArg[1] = {nullptr};
    napi_create_string_utf8(env, callbackData->result.c_str(), callbackData->result.length(), &callbackArg[0]);
    napi_value callback = nullptr;
    napi_get_reference_value(env, callbackData->callbackRef, &callback);

    napi_value result;
    napi_value undefined;
    napi_get_undefined(env, &undefined);
    napi_call_function(env, undefined, callback, 1, callbackArg, &result);

    napi_delete_reference(env, callbackData->callbackRef);
    napi_delete_async_work(env, callbackData->asyncWork);
    delete callbackData;
}

static napi_value SysCallSync(napi_env env, napi_callback_info info) {
    size_t argc = 2;
    napi_value args[2];
    napi_get_cb_info(env, info, &argc, args, nullptr, nullptr);

    char cmd[2048] = {0};
    size_t cmd_size = 0;
    memset(cmd, 0, sizeof(cmd));
    if (napi_ok != napi_get_value_string_utf8(env, args[0], cmd, sizeof(cmd), &cmd_size)) {
        OH_LOG_Print(LOG_APP, LOG_ERROR, 1, "gitGuiCpp", "[SysCallSync] get cmd failed");
        return nullptr;
    }

    char path[1024] = {0};
    size_t path_size = 0;
    memset(path, 0, sizeof(path));
    if (napi_ok != napi_get_value_string_utf8(env, args[1], path, sizeof(path), &path_size)) {
        OH_LOG_Print(LOG_APP, LOG_ERROR, 1, "gitGuiCpp", "[SysCallSync] get path failed");
        return nullptr;
    }

    std::string output;
    exec_cmd(std::string(cmd), std::string(path), output);

    napi_value result;
    napi_create_string_utf8(env, output.c_str(), output.length(), &result);

    return result;
}

static napi_value SysCallAsync(napi_env env, napi_callback_info info) {
    size_t argc = 3;
    napi_value args[3];
    napi_get_cb_info(env, info, &argc, args, nullptr, nullptr);
    auto asyncContext = new CallbackData();

    char cmd[2048] = {0};
    size_t cmd_size = 0;
    memset(cmd, 0, sizeof(cmd));
    if (napi_ok != napi_get_value_string_utf8(env, args[0], cmd, sizeof(cmd), &cmd_size)) {
        OH_LOG_Print(LOG_APP, LOG_ERROR, 1, "gitGuiCpp", "[SysCall] get cmd failed");
        delete asyncContext;
        return nullptr;
    }

    char path[1024] = {0};
    size_t path_size = 0;
    memset(path, 0, sizeof(path));
    if (napi_ok != napi_get_value_string_utf8(env, args[1], path, sizeof(path), &path_size)) {
        OH_LOG_Print(LOG_APP, LOG_ERROR, 1, "gitGuiCpp", "[SysCall] get path failed");
        delete asyncContext;
        return nullptr;
    }

    asyncContext->args[0] = std::string(cmd);
    asyncContext->args[1] = std::string(path);

    napi_create_reference(env, args[2], 1, &asyncContext->callbackRef);
    napi_value resourceName = nullptr;
    napi_create_string_utf8(env, "asyncWorkCallback", NAPI_AUTO_LENGTH, &resourceName);

    napi_create_async_work(env, nullptr, resourceName, SysCallExecuteCB, SysCallCompleteCB, asyncContext,
                           &asyncContext->asyncWork);

    napi_queue_async_work(env, asyncContext->asyncWork);
    return nullptr;
}

static napi_value SysCallBuf(napi_env env, napi_callback_info info) {
    size_t argc = 3;
    napi_value args[3];
    napi_get_cb_info(env, info, &argc, args, nullptr, nullptr);
    auto asyncContext = new CallbackData();

    char cmd[2048] = {0};
    size_t cmd_size = 0;
    memset(cmd, 0, sizeof(cmd));
    if (napi_ok != napi_get_value_string_utf8(env, args[0], cmd, sizeof(cmd), &cmd_size)) {
        OH_LOG_Print(LOG_APP, LOG_ERROR, 1, "gitGuiCpp", "[SysCallBuf] get cmd failed");
        delete asyncContext;
        return nullptr;
    }

    char path[1024] = {0};
    size_t path_size = 0;
    memset(path, 0, sizeof(path));
    if (napi_ok != napi_get_value_string_utf8(env, args[1], path, sizeof(path), &path_size)) {
        OH_LOG_Print(LOG_APP, LOG_ERROR, 1, "gitGuiCpp", "[SysCallBuf] get path failed");
        delete asyncContext;
        return nullptr;
    }

    asyncContext->args[0] = std::string(cmd);
    asyncContext->args[1] = std::string(path);

    napi_create_reference(env, args[2], 1, &asyncContext->callbackRef);
    napi_value resourceName = nullptr;
    napi_create_string_utf8(env, "asyncWorkCallback", NAPI_AUTO_LENGTH, &resourceName);

    napi_create_async_work(env, nullptr, resourceName, SysCallBufExecuteCB, SysCallBufCompleteCB, asyncContext,
                           &asyncContext->asyncWork);

    napi_queue_async_work(env, asyncContext->asyncWork);
    return nullptr;
}

static void SysCallByLineExecCB(napi_env env, void *data) {
    CallbackData *callbackData = reinterpret_cast<CallbackData *>(data);

    auto line_callback = [callbackData](const std::string &line) {
        char *str = (char *)malloc(line.length() + 1);
        strcpy(str, line.c_str());
        napi_call_threadsafe_function(callbackData->safe_func, str, napi_tsfn_blocking);
    };

    exec_cmd(callbackData->args[0], callbackData->args[1], line_callback);
}

static napi_value SysCallByLine(napi_env env, napi_callback_info info) {
    size_t argc = 3;
    napi_value args[3];
    napi_get_cb_info(env, info, &argc, args, nullptr, nullptr);
    auto asyncContext = new CallbackData();

    char cmd[2048] = {0};
    size_t cmd_size = 0;
    memset(cmd, 0, sizeof(cmd));
    if (napi_ok != napi_get_value_string_utf8(env, args[0], cmd, sizeof(cmd), &cmd_size)) {
        OH_LOG_Print(LOG_APP, LOG_ERROR, 1, "gitGuiCpp", "[SysCallByLine] get cmd failed");
        delete asyncContext;
        return nullptr;
    }

    char path[1024] = {0};
    size_t path_size = 0;
    memset(path, 0, sizeof(path));
    if (napi_ok != napi_get_value_string_utf8(env, args[1], path, sizeof(path), &path_size)) {
        OH_LOG_Print(LOG_APP, LOG_ERROR, 1, "gitGuiCpp", "[SysCallByLine] get path failed");
        delete asyncContext;
        return nullptr;
    }

    asyncContext->args[0] = std::string(cmd);
    asyncContext->args[1] = std::string(path);
    asyncContext->env = env;

    napi_create_reference(env, args[2], 1, &asyncContext->callbackRef);

    napi_value resourceName;
    napi_create_string_utf8(env, "asyncWorkCallback", NAPI_AUTO_LENGTH, &resourceName);

    napi_create_threadsafe_function(env, args[2], nullptr, resourceName, 0, 1, nullptr, nullptr, nullptr, CallJs,
                                    &asyncContext->safe_func);
    napi_create_async_work(env, nullptr, resourceName, SysCallByLineExecCB, SysCallByLineCompleteCB, asyncContext,
                           &asyncContext->asyncWork);
    napi_queue_async_work(env, asyncContext->asyncWork);

    return nullptr;
}

EXTERN_C_START
static napi_value Init(napi_env env, napi_value exports) {
    napi_property_descriptor desc[] = {
        {"syscall_sync", nullptr, SysCallSync, nullptr, nullptr, nullptr, napi_default, nullptr},
        {"syscall_async", nullptr, SysCallAsync, nullptr, nullptr, nullptr, napi_default, nullptr},
        {"syscall_buf", nullptr, SysCallBuf, nullptr, nullptr, nullptr, napi_default, nullptr},
        {"syscall_by_line", nullptr, SysCallByLine, nullptr, nullptr, nullptr, napi_default, nullptr}};

    napi_define_properties(env, exports, sizeof(desc) / sizeof(desc[0]), desc);
    return exports;
}
EXTERN_C_END

static napi_module gitGuiModule = {
    .nm_version = 1,
    .nm_flags = 0,
    .nm_filename = nullptr,
    .nm_register_func = Init,
    .nm_modname = "entry",
    .nm_priv = ((void *)0),
    .reserved = {0},
};

extern "C" __attribute__((constructor)) void RegisterEntryModule(void) { napi_module_register(&gitGuiModule); }
